﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// This filter will be used to detect symmetries.
    /// </summary>
    public class Symmetry : BaseImageFilter
    {
        protected int[] input;
        protected double[] direction;
        protected int[] output;
        protected double[] magnitude;
        protected double[] logOfInput;

        protected double[,] distanceLookup;
        protected double[,] phaseLookup;

        protected double sigma;
        protected double mu;

        protected double progress;
        protected int nWidth;
        protected int nHeight;
        protected double distanceWeightingMultiplier;

        /// <summary>
        /// Initializes a new instance of the <see cref="Symmetry"/> class.
        /// </summary>
        public Symmetry()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Symmetry"/> class.
        /// </summary>
        /// <param name="_symmetr">The _symmetry.</param>
        internal Symmetry(Symmetry _symmetry)
        {
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            nWidth = _bitmap.Width;
            nHeight = _bitmap.Height;
            int SIGMA_MIN = 0;
            int SIGMA_MAX = 80;
            int SIGMA_INIT = 20;
            int sigma = SIGMA_INIT;

            double maxDir = 0, minDir = 1000;

            double[] direction = new double[nWidth * nHeight];
            int[] direction2 = new int[nWidth * nHeight];

            int Xi = 0;
            int Xj = 0;
            int Yi = 0;
            int Yj = 0;

            //normalize angles - either 0-255 or 0-180
            double ratioDir = 255 / (Math.PI * 2);
            double ratioDir2 = 180 / (Math.PI * 2);
            for (Xi = 1; Xi < nWidth - 1; Xi++)
            {
                for (Xj = 1; Xj < nHeight - 1; Xj++)
                {
                    int M = (int)((direction[Xj * nWidth + Xi]) * ratioDir);
                    direction[Xj * nWidth + Xi] = ((direction[Xj * nWidth + Xi]) * ratioDir2);
                    if (maxDir < direction[Xj * nWidth + Xi]) maxDir = direction[Xj * nWidth + Xi];
                    if (minDir > direction[Xj * nWidth + Xi]) minDir = direction[Xj * nWidth + Xi];
                    direction2[Xj * nWidth + Xi] = (M << 16 | M << 8 | M);
                }
            }

            int[] orig = new int[_bitmap.Width * _bitmap.Height];
            Init(orig, direction, (double)sigma, 0f, nWidth, nHeight);

            progress = 0;

            double max = 0;
            double dist, phase;
            double value;

            for (Xi = 1; Xi < nWidth - 1; Xi++)
            {
                progress++;
                for (Xj = 1; Xj < nHeight - 1; Xj++)
                {
                    if (logOfInput[Xj * nWidth + Xi] == 0)
                        continue;
                    for (Yi = 1; Yi < nWidth - 1; Yi++)
                    {
                        for (Yj = 1; Yj < nHeight - 1; Yj++)
                        {
                            if (logOfInput[Yj * nWidth + Yi] == 0 || (Xi == Yi && Xj == Yj))
                                continue;
                            if (logOfInput[Xj * nWidth + Xi] < 1) continue;
                            if (logOfInput[Yj * nWidth + Yi] < 1) continue;
                            dist = distanceLookup[Math.Abs(Xi - Yi), Math.Abs(Xj - Yj)];
                            if (dist < 0.01) continue;
                            phase = (float)(1 - Math.Cos(direction[Xj * nWidth + Xi] + direction[Yj * nWidth + Yi] - 2 * Math.Atan(Yi - Yj / Xi - Xj))) * (1 - (float)Math.Cos(direction[Xj * nWidth + Xi] - direction[Yj * nWidth + Yi]));
                            if (phase < 0.1) continue;
                            value = dist * phase * logOfInput[Xj * nWidth + Xi] * logOfInput[Yj * nWidth + Yi];
                            magnitude[Midpoint(Xj, Yj) * nWidth + Midpoint(Xi, Yi)] += (value);
                        }
                    }

                }
            }
            for (Xi = 1; Xi < nWidth - 1; Xi++)
            {
                for (Xj = 1; Xj < nHeight - 1; Xj++)
                {
                    if (max < magnitude[Xj * nWidth + Xi])
                        max = magnitude[Xj * nWidth + Xi];
                }
            }

            // normalising ratio
            double ratio = (double)255 / (double)max;
            System.Console.WriteLine(ratio + "=ratio");


            // normalise values
            for (int x = 1; x < nWidth - 1; x++)
            {
                for (int y = 1; y < nHeight - 1; y++)
                {
                    magnitude[y * nWidth + x] = (int)(magnitude[y * nWidth + x] * ratio);
                    int M = (int)magnitude[y * nWidth + x];
                    _bitmap.SetPixel(x, y, Color.FromArgb(M)); //output[y * width + x] = 0xFF << 24 | (M << 16 | M << 8 | M);
                }
            }

            return _bitmap;
        }

        /// <summary>
        /// Midpoints the specified x1.
        /// </summary>
        /// <param name="x1">The x1.</param>
        /// <param name="x2">The x2.</param>
        /// <returns></returns>
        public int Midpoint(int x1, int x2)
        {
            return (int)((x1 + x2) / 2);
        }

        /// <summary>
        /// Inits the specified input in.
        /// </summary>
        /// <param name="inputIn">The input in.</param>
        /// <param name="directionIn">The direction in.</param>
        /// <param name="sigmaIn">The sigma in.</param>
        /// <param name="muIn">The mu in.</param>
        /// <param name="widthIn">The width in.</param>
        /// <param name="heightIn">The height in.</param>
        public void Init(int[] inputIn, double[] directionIn, double sigmaIn, double muIn, int widthIn, int heightIn)
        {
            nWidth = widthIn;
            nHeight = heightIn;
            sigma = sigmaIn;
            mu = muIn;

            input = new int[nWidth * nHeight];
            direction = new double[nWidth * nHeight];
            output = new int[nWidth * nHeight];
            magnitude = new double[nWidth * nHeight];
            logOfInput = new double[nWidth * nHeight];

            distanceLookup = new double[nWidth, nHeight];
            phaseLookup = new double[181, 181];

            input = inputIn;
            direction = directionIn;

            distanceWeightingMultiplier = (double)1 / (double)Math.Sqrt(2 * Math.PI * sigma);
            CreateTables();
        }

        /// <summary>
        /// Creates the tables.
        /// </summary>
        public void CreateTables()
        {
            int Xj = 0;
            int Xi = 0;

            //get log magnitude of edges
            for (Xi = 1; Xi < nWidth - 1; Xi++)
            {
                for (Xj = 1; Xj < nHeight - 1; Xj++)
                {
                    int M = (input[Xj * nWidth + Xi] & 0xff);
                    logOfInput[Xj * nWidth + Xi] = Math.Log((double)(1 + M));
                }
            }
            
            //setup distance lookup table
            for (Xi = 1; Xi < nWidth; Xi++)
            {
                for (Xj = 1; Xj < nHeight; Xj++)
                {
                    distanceLookup[Xi, Xj] = distanceWeightingMultiplier * (double)Math.Pow(Math.E, (-(Math.Sqrt((Xi) * (Xi) + (Xj) * (Xj)) / (2 * sigma))));
                }
            }

            //setup angle lookup table
            for (Xi = 1; Xi < 181; Xi++)
            {
                for (Xj = 1; Xj < 181; Xj++)
                {
                    phaseLookup[Xj, Xi] = (1 - Math.Cos(Xi - Xj));
                }
            }
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new Symmetry(this);
        }
    }
}
